﻿using System;
using System.Collections.Generic;
using System.Linq;

using Norm;
using Norm.Responses;
using Norm.Collections;
using Norm.Linq;
using EmitMapper;
using EmitMapper.MappingConfiguration;

using Fuse8.DomainFramework.Domain;
using Fuse8.DomainFramework.DataAccess;
using Fuse8.DomainFramework.Common.Exceptions;
using Fuse8.DomainFramework.Common;

namespace Fuse8.DomainFramework.MongoDbWrapper
{
    public class BaseMongoDbEntityDataRepositoryMapper<TEntity, TRepository> : IEntityDataRepositoryMapper<TEntity, TRepository>
        where TEntity : class, IEntityObject<string>, new()
        where TRepository : class, TEntity, IMongoDbRepositoryEntity, new()
    {
        #region Fields

        private DefaultMapConfig _mappingConfigurator;

        private ObjectsMapper<TEntity, TRepository> _entityMapper;

        private ObjectsMapper<TRepository, TEntity> _repositoryMapper;

        #endregion

        #region Properties

        /// <summary>
        /// 
        /// </summary>
        protected virtual IMongo CurrentDb
        {
            get
            {
                var unitOfWork = UnitOfWorkController<TEntity>.GetCurrentUnitOfWork() as MongoDbUnitOfWork<TEntity>;

                if (unitOfWork == null)
                {
                    throw new UnitOfWorkNotDefinedException();
                }

                return unitOfWork.CurrentDb;
            }
        }

        protected ObjectsMapper<TEntity, TRepository> EntityMapper
        {
            get
            {
                if (_entityMapper == null)
                {
                    _entityMapper = ObjectMapperManager.DefaultInstance.GetMapper<TEntity, TRepository>(_mappingConfigurator);
                }

                return _entityMapper;
            }
        }

        protected ObjectsMapper<TRepository, TEntity> RepositoryMapper
        {
            get
            {
                if (_repositoryMapper == null)
                {
                    _repositoryMapper = ObjectMapperManager.DefaultInstance.GetMapper<TRepository, TEntity>(_mappingConfigurator);
                }

                return _repositoryMapper;
            }
        }

        protected DefaultMapConfig MappingConfigurator
        {
            get
            {
                if (_mappingConfigurator == null)
                {
                    _mappingConfigurator = Prepare(new DefaultMapConfig());
                }

                return _mappingConfigurator;
            }
            set
            {
                _mappingConfigurator = value;
            }
        }

        #endregion

        #region IEntityDataRepositoryMapper<TEntity,TRepository> Members

        public virtual void ToRepositoryEntity(TEntity entity, TRepository repositoryEntity)
        {
            Guard.ArgumentNotNull(entity, "entity");
            Guard.ArgumentNotNull(repositoryEntity, "repositoryEntity");

            repositoryEntity = EntityMapper.Map(entity);
        }

        public virtual void FromRepositoryEntity(TRepository repositoryEntity, TEntity entity)
        {
            Guard.ArgumentNotNull(repositoryEntity, "repositoryEntity");
            Guard.ArgumentNotNull(entity, "entity");            

            entity = RepositoryMapper.Map(repositoryEntity);
        }

        public TEntity CreateEntity()
        {
            return new TEntity();
        }

        public TRepository CreateRepositoryEntity()
        {
            return (TRepository)MongoDbEntityBuilder.CreateInstance<TEntity>();
        }

        public void SetIdentityField(TRepository repositoryEntity, TEntity entity)
        {
            Guard.ArgumentNotNull(entity, "entity");
            Guard.ArgumentNotNull(repositoryEntity, "repositoryEntity");

            entity.Id = repositoryEntity.MongoDbId.ToString();
        }

        #endregion

        #region Methods

        protected DefaultMapConfig Prepare(DefaultMapConfig mappingConfigurator)
        {
            Guard.ArgumentNotNull(mappingConfigurator, "mappingConfigurator");

            mappingConfigurator
                .ConvertUsing<string, ObjectId>(p => new ObjectId(p))
                .ConvertUsing<ObjectId, string>(p => p.ToString());

            return mappingConfigurator;
        }
        
        protected IEnumerable<TRepository> MapReduce(string collectionName, string map, string reduce)
        {
            var repositoryEntity = new TRepository();

            var mapReduce = CurrentDb.Database.CreateMapReduce();

            MapReduceResponse response = mapReduce.Execute(
                new MapReduceOptions(collectionName)
                {
                    Map = map,
                    Reduce = reduce
                });

            var collection = response.GetCollection<MapReduceResult<TRepository>>();

            return collection.AsQueryable().Select(p => p.Value);
        }

        #endregion        
    }
}
